Skip to content

cl: support Gloas alpha.12 progressive SSZ and spectests - #22912

Merged
domiwei merged 42 commits into
mainfrom
kewei/gloas-progressive-alpha12
Aug 5, 2026
Merged

cl: support Gloas alpha.12 progressive SSZ and spectests#22912
domiwei merged 42 commits into
mainfrom
kewei/gloas-progressive-alpha12

Conversation

@domiwei

@domiwei domiwei commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Catch Caplin up to the Gloas alpha.12 consensus fixtures and progressive SSZ layouts used by glamsterdam devnet-7.

This PR contains the progressive SSZ and consensus-spec catch-up only. The related runtime/external-EL fixes are independently based on main in #22683.

Changes

  • Implement progressive hashing and decoding for Gloas blocks, beacon state, execution payloads and requests, attestations, transactions, and data columns.
  • Use canonical progressive-list helpers, partial column-root merkleization, and progressive-container proofs.
  • Bound progressive decoding with config-aware, saturating resource caps while preserving decode-before-transition semantics for semantically unbounded progressive lists.
  • Preserve progressive/static list mode and custom caps through SSZ decode, clone, and standalone JSON-to-SSZ paths.
  • Reject malformed Gloas JSON required fields and oversized progressive-container proof schemas before dereference/indexing.
  • Update Gloas light-client proof depths and execution hash proofs.
  • Preserve and propagate the correct attestation fork version.
  • Follow alpha.12 builder deposit processing, credentials, withdrawal delay, and execution-request limits.
  • Initialize epoch-0 Gloas state with the progressive validators root and canonical execution/bid/body commitments while preserving pre-Gloas genesis behavior.
  • Update consensus fixtures to alpha.12 and extend Gloas fork-choice, churn, rewards, gossip, light-client, malformed-bound, and static-helper coverage.

Validation

  • go test ./cl/spectest ./cl/cltypes ./cl/cltypes/solid ./cl/merkle_tree ./cl/clparams/devgenesis ./cl/beacon/handler -count=1
  • Focused progressive limit, custom-config, max-value, malformed JSON, and JSON-to-canonical-SSZ tests
  • go test -race ./cl/cltypes ./cl/cltypes/solid -count=1
  • make lint repeated clean: 0 issues
  • make erigon integration
  • git diff --check

The commits were replayed onto main at 5d00a6a895. Current head is b3fdfc34de.

Adversarial review

Two independent subagent reviews converged after repeated fix/review rounds. Findings fixed during the final rounds included:

  • attacker-derived progressive decode caps that did not provide an actual resource bound
  • config/preset limits lost through BeaconBody and execution-payload-bid reverse decode and clone paths
  • uint64 configuration limits wrapping during conversion to int
  • standalone bid JSON retaining dynamic list mode and emitting malformed gossip SSZ
  • null nested Gloas fields/list members and commitment lists accepted before later dereference
  • progressive-container proof schemas above 256 fields panicking instead of returning an error

The final audits also covered max+1 decode-before-transition behavior, custom/future configs, clone/copy/cache behavior, malformed cross-boundary objects, and pre-Gloas reverse paths.

GitHub Copilot reviewed all 65 changed files. Its actionable request-index and typo findings were fixed, while the sync.WaitGroup.Go comment was rejected because this repository targets Go 1.25.7 where that API is available. Copilot re-reviewed b3fdfc34de and generated no new comments.

@AskAlexSharov

Copy link
Copy Markdown
Collaborator

Reviewed the full diff. Approve with nits.

The hashing core is sound and I could verify it independently: the light-client branch sizes reproduce from progressiveProof on the 46-field state and 13-field body (FinalizedBranchSizeGloas=9, CurrentSyncCommitteeBranchSizeGloas=11, ExecutionBranchSizeGloas=11 = 2 bid + 1 signed-bid + 8 body), proof ordering is leaf-to-root as SSZ expects, and CI is green with the alpha.12 fixtures including ssz_static/BeaconState and the light-client proof cases.

Should fix

cl/merkle_tree/merkle_root.go:255 — panic format args are swapped.

panic(fmt.Sprintf("Can't create TreeRoot: unsported type %T at index %d", i, obj))

%T gets the index and %d gets the value. The correct version is 150 lines up in HashTreeRoot (..., obj, i). Also unsported -> unsupported.

Worth discussing

The new spectest handlers reimplement the logic they test. gossip.go grows ~400 lines of gossip validation for BLS-to-execution changes, sync committee messages and sync contributions, but erigon already ships bls_to_execution_change_service.go, sync_committee_messages_service.go and sync_contribution_service.go in cl/phase1/network/services/. The new handlers check a test-local reimplementation against the fixtures, so the shipped validators get no coverage from them and the two can drift silently. rewards.go has the same shape but is milder — it does call production BaseReward / EligibleValidatorsIndicies / GetUnslashedIndiciesSet and only re-derives the delta combination, which is hard to avoid since erigon computes the components fused. I would route the gossip handlers through the services if that is feasible. The existing gossipAttesterSlashingHandler set this precedent, but it is worth not extending it. On the plus side, rewards.go removes a t.Skip, which is what the repo policy wants.

solid.Attestation.HashSSZ() now branches on an unexported version that most constructors never set. cl/aggregation/pool_impl.go:114 and :167 build merged aggregates as bare &solid.Attestation{...} with no version, so a pooled Gloas aggregate hashes with the pre-Gloas container layout. I traced the live paths and none of them are consensus-critical today: the block-body root goes through the explicit HashSSZProgressive schema regardless of element version, and this PR correctly plugs the aggregate-and-proof signing and verification paths (gossip service, REST pool handler, devvalidator, ToAttestation). But a getter that silently returns the wrong root is a trap for the next caller. At minimum, propagate the version in the two pool merges — Copy() already preserves it, so the asymmetry is easy to miss.

BitList.HashSSZProgressive mixes two length sources.

bitLength := u.Bits()                          // scans all of u.u, past u.l
packed := append([]byte(nil), u.Bytes()...)    // u.u[:u.l] only
packed = packed[:(bitLength+7)/8]              // can exceed cap(packed) -> panic

HashSSZ derives both from u.u[:u.l] via parseBitlist. If a BitList ever carries a non-zero byte past u.l, the progressive path panics where the old one merely disagreed. Deriving the length from u.Bytes() costs nothing and removes the class.

ProgressiveContainerRoot is exported but only reached through ProgressiveContainerRootAll. The interesting branch — zero-chunk padding at inactive field positions — has no caller and no test. Either add a vector for it or unexport it until #22683 needs it.

ExecutionBranchSizeGloas lost its explanation. The deleted comment (get_generalized_index(BeaconBlockBody, 'signed_execution_payload_bid', 'message', 'parent_block_hash')) was the only thing telling a reader why a method named ExecutionBlockHashMerkleProof proves field 0 = ParentBlockHash. That is the surprising-edge-case category the comment policy keeps. A one-liner is worth it.

Nits

  • NewBeaconBody allocates six non-progressive lists in the struct literal and then resetGloasProgressiveLists() discards all of them on the Gloas path. ExecutionRequests.UnmarshalJSON does the same with five. Move the allocation into the version branch.
  • ProgressiveBitlistRoot and ProgressiveBasicListRoot are byte-for-byte identical, and both copy byte-by-byte where packBits (same package, list.go) already does it with copy.
  • hashPair uses crypto/sha256 directly; everything else in merkle_tree goes through common/crypto.Sha256.
  • progressiveProof makes three defensive copies of the chunk slice per recursion level.
  • pool.go: the three new poolingFailure{Index: len(failures)} should use the request index. It matches the existing convention in that file, so either fix all four together or leave all four.
  • MaxBuilderDepositRequestsPerPayload 256 -> 64, MinBuilderWithdrawabilityDelay 8192 -> 64, BuilderWithdrawalPrefix 0x03 -> 0xB0: only mainnet fixtures are wired up (test-fixtures.json has no cl_minimal), so these rest entirely on the alpha.12 mainnet preset. Worth a second pair of eyes against the spec constants.

Checked and fine

ValidatorSet progressive segment bookkeeping (append/set/copy/clear paths, per-closure hash buffers, no shared-buffer race), beaconStateHasher.run() error plumbing (buffered channel, no deadlock), the SetVersion dirty-leaf list (index 24 is genuinely shared between latestExecutionPayloadHeader and latestBlockHash, so marking it is correct and not a typo), ElementProof on limit-0 progressive lists (both callers are Gloas-guarded), and the cl/ssz/decode.go static-element slicing change (BitVector.EncodingSizeSSZ uses bitCap, so it is safe before decode).

@domiwei
domiwei force-pushed the kewei/gloas-progressive-alpha12 branch from 1b55705 to 0b0a9d8 Compare August 3, 2026 08:46
@domiwei
domiwei requested a review from Copilot August 3, 2026 10:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Caplin/CL code to match consensus-specs v1.7.0-alpha.12 for the Gloas fork, adding progressive SSZ decoding/hashing paths, tightening resource bounds/validation for progressive layouts, and refreshing/expanding consensus spec tests and fixtures accordingly.

Changes:

  • Introduce progressive SSZ hashing/merkleization and progressive list/container helpers across Gloas-related CL types and beacon-state hashing.
  • Add config-aware bounds and malformed-input rejection (nested nil checks, size/cap validations) across JSON/SSZ decode paths and network/pool handlers.
  • Update and extend consensus spectests (fixtures, gossip, rewards, static SSZ helpers, light-client proofs) to alpha.12.

Reviewed changes

Copilot reviewed 65 out of 65 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test-fixtures.json Bumps consensus-spec fixtures from alpha.11 to alpha.12.
cl/validator/devvalidator/aggregate.go Sets fork version on aggregate-and-proof messages/signatures.
cl/validator/devvalidator/aggregate_test.go Adds coverage ensuring aggregate attestation hashing matches slot fork version.
cl/transition/machine/block.go Adds Gloas operation-count validation before processing operations.
cl/transition/machine/block_gloas_test.go Tests oversized Gloas operation list rejection.
cl/transition/impl/eth2/operations.go Bounds execution-request counts in ApplyParentExecutionPayload.
cl/transition/impl/eth2/operations_gloas_test.go Updates builder deposit request test vectors for new required fields.
cl/ssz/decode.go Fixes static-sized SSZ element decode to slice exact size.
cl/spectest/consensus_tests/ssz_static_helpers.go Adds Go SSZ implementations for formerly-unimplemented static-helper types.
cl/spectest/consensus_tests/rewards.go Implements rewards spectest runner (removes prior skip) and delta decoding/compare.
cl/spectest/consensus_tests/rewards_test.go Adds unit tests for reward-deltas SSZ decoding bounds/valid cases.
cl/spectest/consensus_tests/light_client.go Adds handler support for Gloas execution block hash merkle proof.
cl/spectest/consensus_tests/gossip.go Implements additional gossip spectest validators (BLS-to-exec, sync committee, contributions).
cl/spectest/consensus_tests/gossip_bounds_test.go Adds bound/overflow tests for new gossip helpers.
cl/spectest/consensus_tests/appendix.go Wires new/expanded formats and enables new static-helper handlers.
cl/phase1/network/services/aggregate_and_proof_service.go Adds nested nil checks; enforces attestation config validation and version propagation.
cl/phase1/network/services/aggregate_and_proof_service_test.go Adds coverage for malformed nested aggregate-and-proof inputs.
cl/phase1/forkchoice/on_attester_slashing.go Uses centralized indexed-attestation indices validation.
cl/phase1/forkchoice/checkpoint_state.go Uses config/version-aware indices validation for indexed attestations.
cl/phase1/core/state/raw/setters.go Invalidates roots and toggles validator hashing mode when crossing Gloas boundary.
cl/phase1/core/state/raw/hashing.go Implements Gloas progressive container roots/proofs; switches some leaves to progressive hashing.
cl/phase1/core/state/raw/hashing_gloas_test.go Adds tests for progressive hashing and SetVersion invalidation semantics.
cl/phase1/core/state/epbs.go Updates builder credential logic and builder deposit request processing rules.
cl/phase1/core/state/epbs_test.go Updates tests for configured builder prefix and updated withdrawable-epoch behavior.
cl/phase1/core/state/accessors.go Adds config/version-aware ValidateIndexedAttestationIndices helper and uses it.
cl/phase1/core/state/accessors_gloas_test.go Tests oversized Gloas attesting indices are rejected before lookup.
cl/merkle_tree/merkle_root.go Adds progressive container roots/proofs and progressive list roots; adds local hashPair.
cl/merkle_tree/merkle_root_test.go Adds coverage for oversized progressive-container proof schema rejection.
cl/cltypes/solid/vector_test.go Adjusts test helper to ensure aggregation bitlist has delimiter bit set.
cl/cltypes/solid/validator_set.go Adds progressive validator-set hashing with segmented progressive merkle caches.
cl/cltypes/solid/validator_set_progressive_test.go Adds progressive root reference tests and mode-switching tests.
cl/cltypes/solid/uint64_raw_list.go Adds SSZ decode bound checks and progressive hash.
cl/cltypes/solid/uint64_raw_list_test.go Tests uint64 raw list decode rejects partial elements/over-limit.
cl/cltypes/solid/transactions.go Adds progressive hashing for transactions list.
cl/cltypes/solid/participation_bitlist.go Adds progressive hashing for participation bitlist.
cl/cltypes/solid/list_ssz.go Introduces progressive list mode with resource-guard decode limits and progressive hashing.
cl/cltypes/solid/list_ssz_test.go Adds tests ensuring progressive list decode enforces the configured limit.
cl/cltypes/solid/byte_list.go Adds progressive hashing for ByteListSSZ.
cl/cltypes/solid/bitvector.go Adds strict size/unused-bits validation and a ValidateSize helper.
cl/cltypes/solid/bitvector_test.go Adds tests for bitvector invalid size/unused bits rejection.
cl/cltypes/solid/bitlist.go Adds progressive hashing and enforces canonical/limited SSZ decoding.
cl/cltypes/solid/bitlist_test.go Adds tests for non-canonical bitlist encoding and limit acceptance.
cl/cltypes/solid/attestation.go Adds version tracking, config validation, and progressive hashing for attestations.
cl/cltypes/solid/attestation_config_test.go Adds tests for config-aware committee-bits size checks and JSON normalization.
cl/cltypes/slashings.go Adds progressive hashing for AttesterSlashing.
cl/cltypes/partial_data_column.go Switches some Gloas sidecar lists to progressive list types and progressive hashing.
cl/cltypes/light_client.go Updates/extends Gloas light-client proof branch sizes.
cl/cltypes/indexed_attestation.go Adds version tracking and progressive hashing for indexed attestations.
cl/cltypes/gloas_progressive_hash_test.go Adds a regression test for progressive block/body roots with a fixed encoded fixture.
cl/cltypes/execution_requests.go Makes execution request lists progressive-aware and adds JSON nil-element validation/coalescing.
cl/cltypes/eth1_block.go Implements Gloas-specific progressive hashing for execution payload header.
cl/cltypes/epbs_payload.go Switches several hashes/proofs to progressive containers; adds JSON validation for payload bids.
cl/cltypes/epbs_payload_test.go Adds tests for preserving progressive limits through decode/clone/JSON paths.
cl/cltypes/column_sidecar.go Uses progressive list types for Gloas columns/proofs and resets on DecodeSSZ.
cl/cltypes/beacon_block.go Refactors per-config limits, adds Gloas progressive list initialization, and implements Gloas progressive body hashing/proofs.
cl/cltypes/beacon_block_test.go Adds tests for null-required-field rejection and config-aware progressive list limits.
cl/cltypes/beacon_block_blinded.go Applies attestation config validation post-decode.
cl/cltypes/aggregate.go Adds version propagation into nested objects and JSON handling that preserves versions.
cl/cltypes/aggregate_gloas_test.go Adds tests around version preservation and nil-nesting behavior for aggregates.
cl/clparams/devgenesis/devgenesis.go Initializes Gloas genesis with progressive validators root and execution payload bid/requests root.
cl/clparams/devgenesis/devgenesis_test.go Adds Gloas genesis tests validating progressive validators root and bid/body root wiring.
cl/clparams/config.go Updates mainnet Gloas builder limits/prefix/min delay to alpha.12 values.
cl/beacon/handler/pool.go Adds aggregate-and-proof request validation and version/config-based checks before gossip.
cl/beacon/handler/pool_test.go Updates attester slashing test setup to use constructor with version-aware fields.
cl/beacon/handler/block_production.go Validates attestations for config/version when publishing blinded blocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cl/phase1/core/state/raw/hashing.go
Comment thread cl/merkle_tree/merkle_root.go
Comment thread cl/beacon/handler/pool.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 65 changed files in this pull request and generated no new comments.

Suppressed comments (2)

cl/phase1/core/state/raw/hashing.go:179

  • sync.WaitGroup has no Go method; this block won’t compile. Use wg.Add(1) + a goroutine with defer wg.Done() (and pass idx/job into the closure).
    cl/merkle_tree/merkle_root.go:250
  • progressiveSchemaRoots allocates and calls BytesRoot even for the common case of 32-byte chunks (e.g., state leaves). For len==32, the SSZ root is the chunk itself, so this can be a straight copy to avoid per-field allocations/work in ProgressiveContainerRoot/Proof paths.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 65 changed files in this pull request and generated no new comments.

Suppressed comments (1)

cl/phase1/core/state/accessors.go:215

  • ValidateIndexedAttestationIndices multiplies two uint64 config limits (MaxValidatorsPerCommittee * MaxCommitteesPerSlot) without overflow protection. With large custom configs this can wrap and produce an incorrect (smaller) limit, causing mis-validation of attesting indices sizes. Use a saturating multiply (cap at MaxUint64) before comparing against inds.Length().

@domiwei
domiwei force-pushed the kewei/gloas-progressive-alpha12 branch from a37a997 to 5b45171 Compare August 3, 2026 23:20
@domiwei

domiwei commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Thanks for the thorough review. I addressed the correctness and robustness items you called out:

  • fixed the progressive container panic formatting and typo;
  • propagated the slot-derived fork version through first-insert and merged aggregation-pool paths, while copying caller-owned attestations and returning aggregation errors;
  • made progressive bitlist hashing, length checks, and merges operate on logical bytes, including compact-to-padded and padded-to-compact regression tests;
  • added an inactive-field ProgressiveContainerRoot vector;
  • restored the one-line Gloas parent-block-hash proof explanation;
  • added nil/config and saturating-limit coverage for indexed attestations.

The gossip spectest-handler consolidation is intentionally left out of this PR because it is a broader production-service refactor; this PR keeps the fixture coverage without mixing that architectural change into the alpha.12 catch-up.

The branch is now rebased onto current main. Focused CL/spectest/network tests, repeated lint, and the required Erigon/integration builds pass locally. The refreshed GitHub checks are running now. Please take another look when convenient.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 67 changed files in this pull request and generated no new comments.

@domiwei
domiwei marked this pull request as ready for review August 3, 2026 23:57
@yperbasis yperbasis added the Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 label Aug 4, 2026

@yperbasis yperbasis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Verified locally beyond reading the diff: full CL spectest suite against the new alpha.12 fixtures passes (8923 cases, 0 failures; all 1258 Gloas cases run with 0 skips), unit tests green in every touched package, and I hand-checked the operation-count asserts, builder-deposit semantics and the three light-client branch depths against the v1.7.0-alpha.12 spec text.

Findings below. Only the first is worth fixing before merge; nothing touches consensus correctness.

Null blob commitment panics the standalone bid handler

ExecutionPayloadBid.UnmarshalJSON rejects a missing or null blob_kzg_commitments list but accepts [null], producing a one-element list holding a nil *KZGCommitment. HashSSZ then nil-derefs — there is no recover() anywhere in cl/merkle_tree.

Reachable path from POST /eth/v1/beacon/execution_payload_bid:

ProcessMessagevalidateBidStateless (passes: only checks Len() against the blob schedule) → validateHighestBidmatchingProposerPreferences returns not-available → queuePendingBidpendingBidKeyFor (execution_payload_bid_service.go:523, root, _ := msg.HashSSZ()) → panic.

Scope: REST only. SSZ static-list decode allocates every element via Clone(), so gossip cannot produce nil members. It is also not a node crash — this runs on the net/http handler goroutine, which recovers per connection, and the panic happens during key computation before queue insertion, so a poisoned bid never reaches the background bid loop. Net effect is a dropped connection plus a stack trace in the logs.

Re-encode is already defended, incidentally: ssz2.MarshalSSZ recovers at cl/ssz/encode.go:73 and returns an ordinary error, so the gossip-publish branch cleanly 500s. Only the hash path is exposed.

Fix: validate every commitment in ExecutionPayloadBid.UnmarshalJSON and return 400, matching what the body decoder in beacon_block.go already does for this exact shape.

Arbitrary decode cap on a spec-unbounded deposits list

progressiveDecodeLimit turns the Electra max of 8192 into a hard decode cap of 16384. Gloas defines requests.deposits as an unbounded ProgressiveList, and it is the one request list apply_parent_execution_payload deliberately does not assert on — the other four have real transition asserts backstopping their 2x caps, which is why the arbitrary bound is only visible here.

Reproduced: 16384 deposits decode; 16385 fails with ErrTooBigList at 3,145,940 encoded bytes, well under MaxChunkSize (15 MiB).

Practically unreachable on mainnet — roughly 16k deposit-contract calls at about 50k gas each is on the order of 800M gas against a ~45M limit — so this is hygiene rather than a live bug. Worth noting that test_deposit_requests_greater_than_electra_max uses 8193 deposits, which is under the cap and passes, consistent with the clean suite run above; the test establishes that the field is unbounded, not that we reject a conformance case.

Fix as suggested: derive the bound from the enclosing message-size limit rather than semanticLimit * 2. For static progressive lists the decoder already knows len(buf), so len(buf)/bytesPerElement is the natural bound and the message-size limit does the real work.

Smaller items

  • BeaconBody.DecodeSSZ (cl/cltypes/beacon_block.go:963) resets lists to progressive when decoding a Gloas body, but decoding a pre-Gloas body into a struct that previously held a Gloas body keeps the progressive lists and would hash with the wrong scheme. ExecutionRequests.DecodeSSZ handles both directions. Latent today — every call site constructs bodies fresh via NewBeaconBody/NewSignedBeaconBlock — but cheap to make symmetric.
  • Dead check in aggregate_and_proof_service.go: the explicit aggregate.CommitteeBits == nil test after ValidateForConfig is unreachable, since ValidateForConfig already rejects nil committee bits at Electra and later.
  • hashPair uses stdlib crypto/sha256 while the rest of cl/merkle_tree uses the pooled common/crypto.Sha256. It sits on the state-root and body-root hot paths.
  • maxPayloadAttestationsForConfig reads the global clparams.GetBeaconConfig() as a fallback layer; the explicit beaconCfg parameter plus the mainnet constant would be more deterministic in tests. Related: the ptcSize fallback block is copy-pasted three times in beacon_block.go, and ExecutionRequests.UnmarshalJSON repeats five near-identical null-element blocks — both want a small helper.
  • PR description is stale: it says head is b3fdfc34de, but 3cc88f219c and 5b45171797 landed after (both reviewed, both fine). Branch is also 10 commits behind main; CI is green on the current base.

@domiwei
domiwei added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 22ec7a4 Aug 5, 2026
250 of 254 checks passed
@domiwei
domiwei deleted the kewei/gloas-progressive-alpha12 branch August 5, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Glamsterdam https://eips.ethereum.org/EIPS/eip-7773

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants